package com.guoguo.sincemusic.logic

import androidx.lifecycle.liveData
import com.guoguo.sincemusic.logic.dao.Storage
import com.guoguo.sincemusic.logic.netwrok.ApplicationNetWork
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlin.coroutines.CoroutineContext

/**
 * 仓库层的统一封装入口
 */
object Repository {
    /**
     *  @第一行代码
     *，由于在Repository封装的每个网络请求接口都可能会抛出异常，
     * 于是我们必须在仓库层中为每个网络请求都进行try catch处理，这无疑增加了仓库层代码实现的复杂度。
     * 然而之前我就说过，其实完全可以在某个统一的入口函数中进行封装，使得只要进行一次try catch处理就行了
     * fire函数
     * 这是一个按照liveData()函数的参数接收标准定义的一个高阶函数。在fire()函数的内部会先调用一下liveData()函数，
     * 然后在liveData()函数的代码块中统一进行了try catch处理，并在try语句中调用传入的Lambda表达式中的代码，
     * 最终获取Lambda表达式的执行结果并调用emit()方法发射出去。另外还有一点需要注意，在liveData()函数的代码块中，
     * 我们是拥有挂起函数上下文的，可是当回调到Lambda表达式中，代码就没有挂起函数上下文了，但实际上Lambda表达式中的代码一定也是在挂起函数中运行的。
     * 为了解决这个问题，我们需要在函数类型前声明一个suspend关键字，以表示所有传入的Lambda表达式中的代码也是拥有挂起函数上下文的。
     * 定义好了fire()函数之后，剩下的工作就很简单了。只需要分别将searchPlaces()和refreshWeather()方法中调用的liveData()函数替换成fire()函数，
     * 然后把诸如trycatch语句、emit()方法之类的逻辑移除即可。这样，仓库层中的代码就变得更加简洁清晰了。
     */
    private fun <T> fire(context: CoroutineContext, block: suspend () -> Result<T>) =
        liveData<Result<T>>(context) {
            val result = try {
                block()
            } catch (e: Exception) {
                Result.failure<T>(e)
            }
            emit(result)
        }

    //登录
    fun login(data: String) = fire(Dispatchers.IO) {
        val res = ApplicationNetWork.login(data)
        if (res.code == 200) {
            // 异步线程,存储用户信息
            Thread { Storage.saveUserInfo(res) }.start()
            Result.success(res.account)
        } else Result.failure(RuntimeException("response status is ${res.code}"))
    }

    /**
     * @第一行代码
     * 如果同时调用多个接口，这两个请求是没有先后顺序的，因此让它们并发执行可以提升程序的运行效率，
     * 但是要在同时得到它们的响应结果后才能进一步执行程序。这种需求有没有让你想起什么呢？
     * 没错，这不恰好就是我们在第11章学习协程时使用的async函数的作用吗？只需要分别在两个async函数中发起网络请求，
     * 然后再分别调用它们的await()方法，就可以保证只有在两个网络请求都成功响应之后，才会进一步执行程序。
     * 另外，由于async函数必须在协程作用域内才能调用，所以这里又使用coroutineScope函数创建了一个协程作用域。
     */
    fun refreshWeather(data: String) = fire(Dispatchers.IO) {
        coroutineScope {
            val infoOne = async {
                ApplicationNetWork.refreshOne(data)
            }
            val infoTwo = async {
                ApplicationNetWork.refreshTwo(data)
            }
            val infoOneResponse = infoOne.await()
            val infoTwoResponse = infoTwo.await()
            if (infoOneResponse.data.isNotEmpty() && infoTwoResponse.data.isNotEmpty()) {
                val weather = TestInfoAll(infoOneResponse.data, infoTwoResponse.data)
                Result.success(weather)
            } else {
                Result.failure(
                    RuntimeException(
                        "infoOne response is faile" + "nfoTwo response is faile"
                    )
                )
            }
        }
    }
}

//将同时请求的接口返回的数据封装起来
data class TestInfoAll(val infoOne: String, val infoTwo: String)

data class InfoOne(val data: String)
data class InfoTwo(val data: String)